package com.github.lzm320a99981e.autocoding.domain.mybatis;

import com.github.lzm320a99981e.autocoding.domain.Entity;
import com.github.lzm320a99981e.autocoding.domain.Pageable;
import com.github.lzm320a99981e.autocoding.domain.Pagination;
import com.github.lzm320a99981e.autocoding.domain.Treeable;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 树状结构 业务层 基础接口
 *
 * @param <R>  Repository
 * @param <E>  com.github.lzm320a99981e.component.basicmodel.Entity
 * @param <ID> PrimaryKey
 */
public abstract class TreeableService<R extends TreeableRepository<E, ID>, E extends Treeable<ID>, ID> extends BaseService<R, E, ID> {
    private static final String PATH_SEPARATOR = "/";

    @Override
    public int insert(E entity) {
        return this.insertSelective(entity);
    }

    @Override
    public int insertSelective(E entity) {
        beforeInsert(entity);
        int i = super.insertSelective(entity);
        if (i > 0) {
            afterInsert(entity);
        }
        return i;
    }

    @Override
    public int insertBatch(List<E> entities) {
        int i = 0;
        for (E entity : entities) {
            i += this.insertSelective(entity);
        }
        return i;
    }

    @Override
    public int updateByPrimaryKey(E entity) {
        return this.updateByPrimaryKeySelective(entity);
    }

    @Override
    public int updateByPrimaryKeySelective(E entity) {
        beforeUpdate(entity);
        return super.updateByPrimaryKeySelective(entity);
    }

    @Override
    public int updateBatch(List<E> entities) {
        int i = 0;
        for (E entity : entities) {
            i += this.updateByPrimaryKeySelective(entity);
        }
        return i;
    }

    @Override
    public int deleteByPrimaryKey(ID id) {
        final E e = selectByPrimaryKey(id);
        final List<E> children = selectChildren(e, true);
        if (Objects.nonNull(children) && !children.isEmpty()) {
            super.deleteByPrimaryKeys(children.stream().map(Entity::getId).collect(Collectors.toList()));
        }
        return super.deleteByPrimaryKey(e.getId());
    }

    @Override
    public int deleteByPrimaryKeys(Collection<ID> ids) {
        ids.forEach(this::deleteByPrimaryKey);
        return ids.size();
    }

    /**
     * 查询根节点
     *
     * @return
     */
    public E selectRoot() {
        return repository.selectRoot();
    }

    /**
     * 交换位置
     *
     * @param source
     * @param target
     * @return
     */
    public int exchange(E source, E target) {
        return repository.exchange(source, target);
    }

    /**
     * 把节点移到顶部
     *
     * @param from
     * @return
     */
    public int moveToTop(E from) {
        return move(from, Position.TOP);
    }

    /**
     * 向上移动节点
     *
     * @param from
     * @return
     */
    public int moveToUp(E from) {
        return move(from, Position.UP);
    }

    /**
     * 向下移动节点
     *
     * @param from
     * @return
     */
    public int moveToDown(E from) {
        return move(from, Position.DOWN);
    }

    /**
     * 把节点移动到底部
     *
     * @param from
     * @return
     */
    public int moveToBottom(E from) {
        return move(from, Position.BOTTOM);
    }

    private int move(E from, Position position) {
        E to = null;
        switch (position) {
            case TOP:
                to = repository.selectFirstSibling(from);
                break;
            case UP:
                to = repository.selectPreviousSibling(from);
                break;
            case DOWN:
                to = repository.selectNextSibling(from);
                break;
            case BOTTOM:
                to = repository.selectLastSibling(from);
                break;
        }
        // 没有找到目标行（两种可能：1.当前行在顶部，向上移动 2.当前行在底部，向下移动）
        if (null == to) {
            return 0;
        }
        // 同一行记录
        if (to.getId().equals(from.getId())) {
            return 0;
        }
        return repository.move(from, to);
    }

    private enum Position {
        TOP,    // 移动到顶部
        UP,     // 向上移动
        DOWN,   // 向下移动
        BOTTOM  // 移动到底部
    }

    /**
     * 把 from 节点 移到 to 节点内部
     *
     * @param from
     * @param to
     */
    public void append(E from, E to) {
        // 同一个节点
        if (from.getId().equals(to.getId())) {
            return;
        }
        // 上级不能追加到下级
        String pathSeparator = to.getExtNodePath().substring(0, 1);
        List<String> pathItems = Arrays.asList(to.getExtNodePath().split(pathSeparator));
        if (pathItems.contains(from.getId().toString())) {
            return;
        }
        // 修改 from 的父节点为 to
        from.setExtNodeParentId(to.getId());
        // 将 from 放置到 to 的最后一个节点，找到to的最后一个子节点, 将from的排序值修改为to的最后一个节点的sort+1
        E lastChildOfTo = repository.selectLastSibling(to);
        Integer sort = lastChildOfTo.getExtNodeSort();
        sort = Objects.isNull(sort) ? 1 : sort + 1;
        from.setExtNodeSort(sort);
        // 修改 from 的路径为 to 的路径加上ID
        String oldPathOfFrom = from.getExtNodePath();
        from.setExtNodePath(to.getExtNodePath() + from.getId() + pathSeparator);
        from.setExtNodeParentId(to.getId());
        // 更新 from 子节点的 path
        repository.updateChildrenPath(from, oldPathOfFrom);
        // 更新 from
        repository.updateByPrimaryKeySelective(from);
    }

    /**
     * 查询目标节点的子节点
     *
     * @param target
     * @param deep
     * @return
     */
    public List<E> selectChildren(E target, boolean deep) {
        return repository.selectChildren(target, deep);
    }

    /**
     * 查询所有父节点
     *
     * @return
     */
    public List<E> selectParents() {
        return repository.selectParents();
    }

    /**
     * 分页+条件 查询子节点
     *
     * @param target
     * @param page
     * @param parameterMap
     * @return
     */
    public Pageable selectChildrenPageByConditions(E target, Pagination page, Map parameterMap) {
        PageInfo pageInfo = PageHelper.offsetPage(page.getOffset(), page.getPageSize()).doSelectPageInfo(() -> repository.selectChildrenByConditions(target, parameterMap));
        page.setTotalRecords(pageInfo.getTotal());
        page.setContent(pageInfo.getList());
        return page;
    }

    /**
     * 插入前操作
     * <pre>
     *     条件：
     *          插入的节点没有父节点
     *     操作：
     *          1. 设置节点层级路径为根路径：/
     *          2. 设置节点位置为：1
     * </pre>
     *
     * @param entity
     */
    private void beforeInsert(E entity) {
        if (Objects.isNull(entity.getExtNodeParentId())) {
            entity.setExtNodeSort(1);
            entity.setExtNodePath(PATH_SEPARATOR);
        }
    }

    /**
     * 插入之后
     * <pre>
     *     1. 调整节点的位置，新增的节点在最后面
     *     2. 设置节点层级路径
     * </pre>
     *
     * @param entity
     */
    private void afterInsert(E entity) {
        E parent = repository.selectByPrimaryKey(entity.getExtNodeParentId());
        if (Objects.nonNull(parent)) {
            // 获取新增节点的最后一个兄弟节点
            E lastSibling = repository.selectLastSibling(entity);
            // 调整节点的位置，新增的节点在最后面
            entity.setExtNodeSort(Objects.isNull(lastSibling) ? 1 : lastSibling.getExtNodeSort() + 1);
            // 设置节点的层级路径
            entity.setExtNodePath(parent.getExtNodePath() + entity.getId() + parent.getExtNodePath().substring(0, 1));
            // 更新节点
            updateByPrimaryKeySelective(entity);
        }
    }

    /**
     * 节点更新前的操作
     * <pre>
     *     条件：
     *          更新前和更新后不在同一层级
     *     操作：
     *          1. 设置新的层级路径
     *          2. 设置新的位置
     *          3. 更新子孙节点的层级路径
     * </pre>
     *
     * @param entity
     */
    private void beforeUpdate(E entity) {
        if (Objects.isNull(entity.getExtNodeParentId())) {
            return;
        }
        // 获取更新前的实体
        E oldEntity = selectByPrimaryKey(entity.getId());
        // 如果更新节点与原节点不在同一层级
        if (!Objects.equals(entity.getExtNodeParentId(), oldEntity.getExtNodeParentId())) {
            // 获取新的父节点
            E newParent = selectByPrimaryKey(entity.getExtNodeParentId());
            // 设置新的层级路径
            entity.setExtNodePath(newParent.getExtNodePath() + entity.getId() + newParent.getExtNodePath().substring(0, 1));
            // 设置新的位置
            E lastSibling = repository.selectLastSibling(entity);
            entity.setExtNodeSort(Objects.isNull(lastSibling) ? 1 : lastSibling.getExtNodeSort() + 1);
            // 更新自身子孙节点的层级路径
            repository.updateChildrenPath(entity, oldEntity.getExtNodePath());
        }
    }

}
